package genetic;


import genetic.q1.TSPGraph;
import genetic.q1.TSPPhenotype;
import genetic.q2.PrisonerPhenotype;
import genetic.q3.RobbyPhenotype;
import genetic.q4.EternityPhenotype;
import genetic.q4.EternityPiece;

import java.io.File;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;

public class GeneticAlgorithm {

	List<Genotype> population;
	int populationSize;
	Phenotype prototype;
	double bestFitness = 0;
	int bestGen = 0;
	Genotype best = null;

	public GeneticAlgorithm(Phenotype prototype){
		this.population = new LinkedList<Genotype>();
		this.prototype = prototype;
	}

	public void init(int populationSize){
		this.populationSize = populationSize;
		population.clear();
		population = prototype.getInitialPopulation(populationSize);
	}

	public Genotype runAlgorithm(int numGenerations, double Pc, double Pm, int elitism, String resultFile){
		PrintWriter out = openFile(resultFile);
		bestFitness = -2000;
		double[] initStats = getStatistics(0);
		out.println(0 + "\t" + initStats[0] + "\t" + initStats[1]);

		for (int generation=1; generation<=numGenerations; generation++){
			List<Genotype> topGun = new LinkedList<Genotype>();
			if(elitism!=0)
				topGun = prototype.elitist(population, populationSize, elitism);
			population = prototype.getMatingPool(population, populationSize-elitism);
			List<Genotype> newPopulation = new LinkedList<Genotype>();
			for (int i=0; i<populationSize-1-elitism; i+=2){	//breed
				Genotype p1 = population.get(i);
				Genotype p2 = population.get(i+1);
				Genotype[] offsprings;

				double rand = Math.random();
				if (rand<Pc){		//create offsprings
					offsprings = prototype.crossover(p1, p2);
				} else {			//copy parents
					offsprings = new Genotype[]{p1.clone(), p2.clone()};
				}

				newPopulation.add(prototype.mutate(offsprings[0], Pm));
				newPopulation.add(prototype.mutate(offsprings[1], Pm));
			}

			for (int i=0; i<topGun.size(); i++){
				newPopulation.add(topGun.get(i));
				
			}
			population = newPopulation;

			prototype.updateFitness(population);

			double[] stats = getStatistics(generation);
			System.out.println("Generation "+generation+": Best - " + stats[0] + ", Average - " + stats[1] + " , Best so far: "+ bestFitness+ "\t" + best);
			out.println(generation + "\t" + stats[0] + "\t" + stats[1]);
		}

		System.out.println("Best genotype on final generation, found on generation "+ bestGen+", fitness - "+bestFitness+ ": " + best);
//		for (int i=0; i<population.size(); i++){
//			System.out.println(population.get(i));
//		}
		
		out.close();

		return best;
	}

	private double[] getStatistics(int gen){
		double maxFitness = -2000;
		double averageFitness = 0;
		Genotype genBest = null;
		for (Genotype g: population){
			double fitness = prototype.fitness(g);
			if(maxFitness < fitness){
				maxFitness = fitness;
				genBest = g;
			}
			averageFitness = averageFitness + fitness;
		}
		averageFitness = averageFitness/(double)population.size();

		if (bestFitness < maxFitness) {
			bestFitness = maxFitness;
			best = genBest;
			bestGen = gen;
		}
		return new double[]{ maxFitness , averageFitness};
	}

	private PrintWriter openFile(String filename){
		try{
			File f = new File(filename);
			f.delete();
			f.createNewFile();
			PrintWriter out = new PrintWriter(f);
			return out;
		}catch (Exception e) {
			return null;
		}
	}

	public static void main(String[] args) {
		int run = 3;
		if (run == 1){
			TSPGraph graph = new TSPGraph("TSP1.txt");
			graph.readFile();
			TSPPhenotype p = new TSPPhenotype(graph.getSize(), graph);
			GeneticAlgorithm ga = new GeneticAlgorithm(p);
			for (int i=1; i<=10; i++){
				ga.init(100);
				ga.runAlgorithm(2000, 0.75, 0.1,0, "r1-"+i+".txt");
			}
		} else if (run == 2){
			GeneticAlgorithm b = new GeneticAlgorithm(new PrisonerPhenotype(1));
			b.init(50);
			b.runAlgorithm(100, 0.7, 0.01,0, "q2-memory1-1.txt");
			b.init(50);
			b.runAlgorithm(100, 0.7, 0.01,0, "q2-memory1-2.txt");
			b.init(50);
			b.runAlgorithm(100, 0.7, 0.01,0, "q2-memory1-3.txt");
			b = new GeneticAlgorithm(new PrisonerPhenotype(2));
			b.init(50);
			b.runAlgorithm(100, 0.7, 0.01,0, "q2-memory2-1.txt");
			b.init(50);
			b.runAlgorithm(100, 0.7, 0.01,0, "q2-memory2-2.txt");
			b.init(50);
			b.runAlgorithm(100, 0.7, 0.01,0, "q2-memory2-3.txt");
			b = new GeneticAlgorithm(new PrisonerPhenotype(3));
			b.init(50);
			b.runAlgorithm(100, 0.7, 0.01,0, "q2-memory3-1.txt");
			b.init(50);
			b.runAlgorithm(100, 0.7, 0.01,0, "q2-memory3-2.txt");
			b.init(50);
			b.runAlgorithm(100, 0.7, 0.01,0, "q2-memory3-3.txt");
		} else if (run == 3){
			GeneticAlgorithm b = new GeneticAlgorithm(new RobbyPhenotype(50));
			b.init(100);
			b.runAlgorithm(1000, 0.7, 0.05,0, "q3.txt");
		} else if (run == 4){
			List<Integer> gray1 = new LinkedList<Integer>();
			List<Integer> gray2 = new LinkedList<Integer>();
			List<EternityPiece> empty = new LinkedList<EternityPiece>();
			int size = 60;
			for (int i=0; i<size; i++){
				gray1.add(EternityPiece.GRAY);
			}
			for (int i=0; i<4; i++){
				gray2.add(EternityPiece.GRAY);
			}
			GeneticAlgorithm b = new GeneticAlgorithm(new EternityPhenotype(gray1, gray2, empty));
			b.init(100);
			b.runAlgorithm(100, 0.7, 0.1,0, "eternity.txt");
		}
	}
}